/*
 * File     : StructureByValueParameter.java
 * Created  : 3 May 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal.call;

import static org.objectweb.asm.Opcodes.*;

import java.util.ArrayList;
import java.util.List;

import org.objectweb.asm.MethodVisitor;

import com.googlecode.dni.internal.DniInternal;
import com.googlecode.dni.internal.MemoryAccess;
import com.googlecode.dni.internal.structure.StructureBuilder;
import com.googlecode.dni.internal.structure.StructureRecord;
import com.googlecode.dni.type.nativetype.NativeTypes;
import com.googlecode.dni.type.structure.IllegalStructureException;
import com.googlecode.dni.type.structure.Structure;

/**
 * <p>
 *  Encapsulates a parameter to a native call that holds a structure on the
 *  stack.  Used by the UNIX-style calling conventions.
 * </p>
 *
 * @author Matthew Wilson
 */
final class StructureByValueOnStackParameter extends Parameter
{

    private final StructureByValueOnStackParameter first;

    private final int offset;

    private final StructureRecord structureRecord;

    private int pointerVarIndex;


    private StructureByValueOnStackParameter( final StructureByValueOnStackParameter first,
                                              final int offset )
    {
        assert first != null;
        assert offset > 0;

        this.first = first;
        this.offset = offset;
        this.structureRecord = null;
    }

    private StructureByValueOnStackParameter( final StructureRecord structureRecord )
    {
        this.first = this;
        this.offset = 0;
        this.structureRecord = structureRecord;
    }

    /**
     * Creates a new instance of all parameters required.
     *
     * @param parameter
     *            the source parameter
     *
     * @return the resultant parameters
     */
    static List< StructureByValueOnStackParameter > create( final StructureByValueParameter parameter )
    {
        Class< ? extends Structure > structType = parameter.getJavaType().asSubclass( Structure.class );
        StructureRecord structureRecord = DniInternal.getStructureRecord( structType );

        int size = structureRecord.getInfo().getSize();
        if ( size <= 0 )
        {
            throw new IllegalStructureException( "Cannot pass opaque structure by value: " + structType.getName() );
        }

        List< StructureByValueOnStackParameter > result = new ArrayList< StructureByValueOnStackParameter >();

        StructureByValueOnStackParameter first = null;
        int offset = 0;
        while ( offset < size )
        {
            StructureByValueOnStackParameter replacement;
            if ( first == null )
            {
                replacement = new StructureByValueOnStackParameter( structureRecord );
                first = replacement;
            }
            else
            {
                replacement = new StructureByValueOnStackParameter( first, offset );
            }

            int remaining = size - offset;
            if ( remaining >= 8 && NativeTypes.POINTER_SIZE == 8 )
            {
                replacement.setNativeType( 'J' );
                offset += 8;
            }
            else if ( remaining >= 3 )
            {
                replacement.setNativeType( 'I' );
                offset += 4;
            }
            else if ( remaining == 2 )
            {
                replacement.setNativeType( 'C' );
                offset += 2;
            }
            else
            {
                assert remaining == 1;
                replacement.setNativeType( 'B' );
                offset++;
            }
            
            replacement.setJavaIndex( parameter.getJavaIndex() );
            replacement.setJavaVarIndex( parameter.getJavaVarIndex() );
            replacement.setJavaType( parameter.getJavaType() );
            
            result.add( replacement );
        }

        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    int getJavaVarSlots()
    {
        // the first one takes up a Java slot, but the others don't
        return this.offset == 0 ? 1 : 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    int allocatePrepareVarSlots( final MethodVisitor methodVisitor, final int varCount )
    {
        // the first one allocates a variable
        if ( this.offset != 0 )
        {
            return 0;
        }

        this.pointerVarIndex = varCount;
        methodVisitor.visitInsn( LCONST_0 );
        methodVisitor.visitVarInsn( LSTORE, this.pointerVarIndex );

        return 2;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writePrepareParameter( final MethodVisitor methodVisitor )
    {
        // the first one sets the variable
        if ( this.offset != 0 )
        {
            return;
        }

        String structureType = this.structureRecord.getImplType().getName().replace( '.', '/' );

        methodVisitor.visitVarInsn( ALOAD, getJavaVarIndex() );
        methodVisitor.visitTypeInsn( CHECKCAST, structureType );
        methodVisitor.visitFieldInsn( GETFIELD,
                                      structureType,
                                      "pointer",
                                      "J" );
        methodVisitor.visitVarInsn( LSTORE, this.pointerVarIndex );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeGetParameter( final MethodVisitor methodVisitor )
    {
        methodVisitor.visitVarInsn( LLOAD, this.first.pointerVarIndex );
        if ( this.offset != 0 )
        {
            StructureBuilder.writeConstantInt( methodVisitor, this.offset );
            methodVisitor.visitInsn( I2L );
            methodVisitor.visitInsn( LADD );
        }

        switch ( getNativeType() )
        {
            case 'B':
                assert ( this.offset & 1 ) == 0;
                methodVisitor.visitMethodInsn( INVOKESTATIC,
                                               MemoryAccess.UNSAFE_ACCESS_TYPE,
                                               "getByte",
                                               "(J)B" );
                // TODO Is this unsigned extension required?
                StructureBuilder.writeConstantInt( methodVisitor, 0xff );
                methodVisitor.visitInsn( IAND );
                break;

            case 'C':
                assert ( this.offset & 1 ) == 0;
                methodVisitor.visitMethodInsn( INVOKESTATIC,
                                               MemoryAccess.UNSAFE_ACCESS_TYPE,
                                               "getChar",
                                               "(J)C" );
                break;

            case 'I':
                assert ( this.offset & 3 ) == 0;
                methodVisitor.visitMethodInsn( INVOKESTATIC,
                                               MemoryAccess.UNSAFE_ACCESS_TYPE,
                                               "getInt",
                                               "(J)I" );
                break;

            case 'J':
                assert ( this.offset & 7 ) == 0;
                methodVisitor.visitMethodInsn( INVOKESTATIC,
                                               MemoryAccess.UNSAFE_ACCESS_TYPE,
                                               "getLong",
                                               "(J)J" );
                break;

            default:
                throw new AssertionError( getNativeType() );
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeReturnParameter( final MethodVisitor methodVisitor, final int returnVarIndex )
    {
        throw new UnsupportedOperationException( "Internal error: missing code to return structures by value" );
    }

}
